/*******************************************************************************************************
 nomacs is a fast and small image viewer with the capability of synchronizing multiple instances
 
 Copyright (C) 2011-2016 Markus Diem <markus@nomacs.org>
 Copyright (C) 2011-2016 Stefan Fiel <stefan@nomacs.org>
 Copyright (C) 2011-2016 Florian Kleber <florian@nomacs.org>

 This file is part of nomacs.

 nomacs is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 nomacs is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.

 related links:
 [1] https://nomacs.org/
 [2] https://github.com/nomacs/
 [3] http://download.nomacs.org
 *******************************************************************************************************/

#include "DkSettingsWidget.h"

#include "DkActionManager.h"

#pragma warning(push, 0)	// no warnings from includes
#include <QTreeView>
#include <QVBoxLayout>
#include <QHeaderView>
#include <QLineEdit>
#include <QMenu>
#pragma warning(pop)

namespace nmc {


// DkSettingsWidget --------------------------------------------------------------------
DkSettingsWidget::DkSettingsWidget(QWidget* parent) : DkWidget(parent) {

	createLayout();

	QMetaObject::connectSlotsByName(this);
}

void DkSettingsWidget::setSettingsPath(const QString & settingsPath, const QString& parentName) {

	QSettings settings(settingsPath, QSettings::IniFormat);

	DkSettingsGroup sg = DkSettingsGroup::fromSettings(settings, parentName);
	addSettingsGroup(sg);
}

void DkSettingsWidget::addSettingsGroup(const DkSettingsGroup & group) {
	
	if (group.name().isEmpty()) {
		for (auto g : group.children())
			mSettingsModel->addSettingsGroup(g);
	}
	else
		mSettingsModel->addSettingsGroup(group);
	
	// udpate proxy
	mProxyModel->setSourceModel(mSettingsModel);
}

void DkSettingsWidget::clear() {
	mProxyModel->clear();
	mSettingsModel->clear();
}

void DkSettingsWidget::changeSetting(QSettings& settings, const QString& key, const QVariant& value, const QStringList& groups) {

	QStringList groupsClean = groups;
	groupsClean.pop_front();	// remove default group: settings

	for (const QString& gName : groupsClean) {
		settings.beginGroup(gName);
	}

	settings.setValue(key, value);
	qDebug() << key << ":" << value << "written...";

	for (int idx = 0; idx < groupsClean.size(); idx++)
		settings.endGroup();
}

void DkSettingsWidget::removeSetting(QSettings& settings, const QString & key, const QStringList & groups) {

	QStringList groupsClean = groups;
	groupsClean.pop_front();

	for (const QString& gName : groupsClean) {
		settings.beginGroup(gName);
	}

	settings.remove(key);
	qDebug() << key << "removed...";

	for (int idx = 0; idx < groupsClean.size(); idx++)
		settings.endGroup();
}

void DkSettingsWidget::on_SettingsModel_settingChanged(const QString & key, const QVariant & value, const QStringList & groups) {
	emit changeSettingSignal(key, value, groups);
}

void DkSettingsWidget::on_SettingsModel_settingRemoved(const QString & key, const QStringList & groups) {
	emit removeSettingSignal(key, groups);
}

void DkSettingsWidget::on_removeRows_triggered() {
	
	QModelIndexList selList = mTreeView->selectionModel()->selectedRows();
	for (const QModelIndex index : selList) {
		const QModelIndex cIndex = mProxyModel->mapToSource(index.parent());
		mSettingsModel->removeRows(index.row(), 1, cIndex);
	}
}

void DkSettingsWidget::createLayout() {

	mSettingsFilter = new QLineEdit(this);
	mSettingsFilter->setObjectName("Filter");
	mSettingsFilter->setPlaceholderText(tr("Filter Settings"));

	// create our beautiful shortcut view
	mSettingsModel = new DkSettingsModel(this);
	mSettingsModel->setObjectName("SettingsModel");

	mProxyModel = new DkSettingsProxyModel(this);
	mProxyModel->setSourceModel(mSettingsModel);
	//mProxyModel->setDynamicSortFilter(true);

	mTreeView = new QTreeView(this);
	mTreeView->setModel(mProxyModel);
	mTreeView->setAlternatingRowColors(true);
	//mTreeView->setIndentation(8);
	mTreeView->header()->resizeSection(0, 200);
	//mTreeView->setSortingEnabled(true);

	QVBoxLayout* layout = new QVBoxLayout(this);
	layout->setContentsMargins(0, 0, 0, 0);
	layout->addWidget(mSettingsFilter);
	layout->addWidget(mTreeView);

	// contextMenu
	QMenu* contextMenu = new QMenu(mTreeView);
	mTreeView->setContextMenuPolicy(Qt::ActionsContextMenu);
	
	QAction* removeAction = new QAction(tr("Delete"), contextMenu);
	removeAction->setObjectName("removeRows");
	removeAction->setShortcut(QKeySequence::Delete);
	mTreeView->addAction(removeAction);
	
}

void DkSettingsWidget::filter(const QString& filterText) {

	if (!filterText.isEmpty())
		mTreeView->expandAll();

	mProxyModel->setFilterRegExp(QRegExp(filterText, Qt::CaseInsensitive, QRegExp::FixedString));
	qDebug() << "filtering: " << filterText;
}

void DkSettingsWidget::expandAll() {
	mTreeView->expandAll();
}

void DkSettingsWidget::on_Filter_textChanged(const QString& filterText) {

	filter(filterText);
}

// DkSettingsEntry --------------------------------------------------------------------
DkSettingsEntry::DkSettingsEntry(const QString & key, const QVariant & value) {
	mKey = key;
	mValue = value;
}

QString DkSettingsEntry::key() const {
	return mKey;
}

void DkSettingsEntry::setValue(const QVariant & value) {
	mValue = value;
}

QVariant DkSettingsEntry::value() const {
	return mValue;
}

DkSettingsEntry DkSettingsEntry::fromSettings(const QString & key, const QSettings & settings) {

	DkSettingsEntry se(key);

	// int settings
	bool ok = false;

	// double settings?
	double dVal = settings.value(key, -1.0).toDouble(&ok);

	// we first cast to double & check if the number is rational
	// if it is not, we pass it to the int cast
	if (ok && (double)qRound(dVal) != dVal) {
		se.setValue(dVal);
		return se;
	}

	int iVal = settings.value(key, -1).toString().toInt(&ok); //double is ok e.g. 1.3 -> iVal=1

	if (ok) {
		se.setValue(iVal);
		return se;
	}
	
	se.setValue(settings.value(key));
	return se;
}

// DkSettingsGroup --------------------------------------------------------------------
DkSettingsGroup::DkSettingsGroup(const QString & name) {
	mGroupName = name;
}

bool DkSettingsGroup::isEmpty() const {
	return mEntries.empty() && mChildren.empty();
}

DkSettingsGroup DkSettingsGroup::fromSettings(QSettings & settings, const QString & groupName) {

	DkSettingsGroup sg(groupName);
	settings.beginGroup(groupName);

	for (const QString& key : settings.allKeys()) {
		if (!key.contains("/"))	// skip entries from different hierarchies
			sg.mEntries << DkSettingsEntry::fromSettings(key, settings);
	}

	for (const QString& gn : settings.childGroups()) {
		sg.addChild(DkSettingsGroup::fromSettings(settings, gn));
	}

	settings.endGroup();

	return sg;
}

QString DkSettingsGroup::name() const {
	return mGroupName;
}

int DkSettingsGroup::size() const {
	return mEntries.size();
}

QVector<DkSettingsEntry> DkSettingsGroup::entries() const {
	return mEntries;
}

QVector<DkSettingsGroup> DkSettingsGroup::children() const {
	return mChildren;
}

void DkSettingsGroup::addChild(const DkSettingsGroup & group) {
	mChildren << group;
}

// DkSettingsProxyModel --------------------------------------------------------------------
DkSettingsProxyModel::DkSettingsProxyModel(QObject* parent) : QSortFilterProxyModel(parent) {

}

bool DkSettingsProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex & sourceParent) const {

	QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);

	TreeItem* t = static_cast<TreeItem*>(index.internalPointer());
	if (t) {
		return t->contains(filterRegExp(), filterKeyColumn());
	}

	return true;
}


// DkSettingsModel --------------------------------------------------------------------
DkSettingsModel::DkSettingsModel(QObject* parent) : QAbstractItemModel(parent) {

	// create root
	QVector<QVariant> rootData;
	rootData << tr("Settings") << tr("Value");

	mRootItem = new TreeItem(rootData);

}

DkSettingsModel::~DkSettingsModel() {
	delete mRootItem;	// crash detected
}

QModelIndex DkSettingsModel::index(int row, int column, const QModelIndex &parent) const {

	if (!hasIndex(row, column, parent))
		return QModelIndex();

	TreeItem *parentItem;

	if (!parent.isValid())
		parentItem = mRootItem;
	else
		parentItem = static_cast<TreeItem*>(parent.internalPointer());

	TreeItem *childItem = parentItem->child(row);

	if (childItem)
		return createIndex(row, column, childItem);
	else
		return QModelIndex();
}

QModelIndex DkSettingsModel::parent(const QModelIndex &index) const {

	if (!index.isValid())
		return QModelIndex();

	TreeItem *childItem = static_cast<TreeItem*>(index.internalPointer());
	TreeItem *parentItem = childItem->parent();

	if (parentItem == mRootItem)
		return QModelIndex();

	//qDebug() << "creating index for: " << childItem->data(0);

	return createIndex(parentItem->row(), 0, parentItem);
}

int DkSettingsModel::rowCount(const QModelIndex& parent) const {

	TreeItem *parentItem;
	if (parent.column() > 0)
		return 0;

	if (!parent.isValid())
		parentItem = mRootItem;
	else
		parentItem = static_cast<TreeItem*>(parent.internalPointer());

	return parentItem->childCount();
}

int DkSettingsModel::columnCount(const QModelIndex& parent) const {

	if (parent.isValid())
		return static_cast<TreeItem*>(parent.internalPointer())->columnCount();
	else
		return mRootItem->columnCount();
}

QVariant DkSettingsModel::data(const QModelIndex& index, int role) const {

	if (!index.isValid()) {
		qDebug() << "invalid row: " << index.row();
		return QVariant();
	}

	if (role == Qt::DisplayRole || role == Qt::EditRole) {

		TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
		return item->data(index.column());
	}

	return QVariant();
}

QVariant DkSettingsModel::headerData(int section, Qt::Orientation orientation, int role) const {

	if (orientation != Qt::Horizontal || role != Qt::DisplayRole) 
		return QVariant();

	return mRootItem->data(section);
} 

bool DkSettingsModel::setData(const QModelIndex& index, const QVariant& value, int role) {

	if (!index.isValid() || role != Qt::EditRole)
		return false;

	if (index.column() == 1) {


		QString val = value.value<QString>();
		// TODO: check value & write it directly?

		TreeItem* item = static_cast<TreeItem*>(index.internalPointer());
		item->setData(val, index.column());
	}
	else {
		TreeItem* item = static_cast<TreeItem*>(index.internalPointer());
		item->setData(value, index.column());
	}

	TreeItem* item = static_cast<TreeItem*>(index.internalPointer());

	if (item) {
		item->setData(value, index.column());

		if (index.column() == 1) {
			emit settingChanged(item->data(0).toString(), item->data(1), item->parentList());
		}
	}

	emit dataChanged(index, index);
	return true;
}

Qt::ItemFlags DkSettingsModel::flags(const QModelIndex& index) const {

	if (!index.isValid())
		return Qt::ItemIsEditable;

	//// no editing on root items
	//if (item->parent() == rootItem)
	//	return QAbstractTableModel::flags(index);

	Qt::ItemFlags flags;

	if (index.column() == 0)
		flags = QAbstractItemModel::flags(index);
	if (index.column() == 1)
		flags = QAbstractItemModel::flags(index) | Qt::ItemIsEditable;

	return flags;
}

void DkSettingsModel::addSettingsGroup(const DkSettingsGroup& group, const QString& parentName) {

	beginResetModel();
	// create root
	QVector<QVariant> data;
	data << group.name();

	TreeItem* parentItem = mRootItem->find(parentName, 0);
	if (!parentItem)
		parentItem = mRootItem;

	TreeItem* settingsItem = new TreeItem(data, parentItem);

	for (const DkSettingsEntry& entry : group.entries()) {

		QVector<QVariant> settingsData;
		settingsData << entry.key() << entry.value();

		TreeItem* dataItem = new TreeItem(settingsData, settingsItem);
		settingsItem->appendChild(dataItem);
	}

	parentItem->appendChild(settingsItem);

	for (const DkSettingsGroup& g : group.children()) {
		addSettingsGroup(g, group.name());
	}
	endResetModel();

	//qDebug() << "item - child count:" << settingsItem->childCount();
}

void DkSettingsModel::clear() {

	beginResetModel();
	mRootItem->clear();
	endResetModel();
}

bool DkSettingsModel::removeRows(int row, int count, const QModelIndex & parent) {

	bool success = false;

	TreeItem* item = static_cast<TreeItem*>(parent.internalPointer());
	if (!item)
		item = mRootItem;

	beginRemoveRows(parent, row, row);
	for (int rIdx = row; rIdx < row + count; rIdx++) {

		TreeItem* deleteRow = item->child(rIdx);

		if (deleteRow) {
			emit settingRemoved(deleteRow->data(0).toString(), deleteRow->parentList());
			item->remove(rIdx);
			success = true;
		}
		else
			qWarning() << "I cannot delete a non-existing row:" << row;
	}
	endRemoveRows();

	return success;
}

}